function serialize_js_obj_to_proto3(obj, pb_type) {
  var YourMessage = proto_root.lookupType(proto_type[pb_type]["mes"]);
  var message = YourMessage.fromObject(obj);
  var buffer = YourMessage.encode(message).finish();
  var blob = new Blob([buffer], { type: "application/octet-stream" });
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  if (pb_type == "avp") {
    link.download = "index." + proto_type["avp"]["app"];
  } else {
    link.download = document.getElementById("current_id_name").value + "." + proto_type[pb_type]["app"];
  }
  link.click();
}

//file input event listener
function fileInputWorker_map(event) {
  load_paths(event.target.files)
}

async function load_paths(files) {
  proto_root = await protobuf.load(proto_files)
  const file = files[0]
  if (!file) {
    console.error("No file selected.");
    return;
  }
  for (var i = 0; i < files.length; i++) {
    const file = files[i];
    const reader = new FileReader();
    var app = file.name.split(".")[1]
    if (app == "nav") {
      reader.onload = function (event) { parse_nav_pb(event.target.result); };
    } else if (app == "sem") {
      reader.onload = function (event) { parse_sem_pb(event.target.result); };
    } else if (app == "loc") {
      reader.onload = function (event) { parse_loc_pb(event.target.result); };
    } else if (app == "rec") {
      reader.onload = function (event) { parse_rec_pb(event.target.result); };
    }
          
    reader.onerror = function() {
      console.log(reader.error);
    };
    reader.readAsArrayBuffer(file);
  }
}


async function load_buffer (buffer) {
  proto_root = await protobuf.load(proto_files)
  if (buffer['sem']) parse_sem_pb(buffer['sem'])
  if (buffer['nav']) parse_nav_pb(buffer['nav'])
  if (buffer['loc']) parse_loc_pb(buffer['loc'])
  if (buffer['rec']) parse_rec_pb(buffer['rec'])
}
// read .sem file
function parse_sem_pb(buffer) {
    console.log("sem",proto_root)
    console.log("sem",proto_type)
  const YourMessage = proto_root.lookupType(proto_type["sem"]["mes"]);
  const message = YourMessage.decode(new Uint8Array(buffer));
  console.log(message)
  remove_all_sub_layers(ele_layer["slot"])
  remove_all_sub_layers(ele_layer["slot_id"])
  var temp_points = []
  for (var slot_id in message.parkingSlots) {
    var points = []
    temp_points.push(points)
    var slot = message.parkingSlots[slot_id]
    var centY = 0.0
    var centX = 0.0
    for (var point in slot.slotPoints) {
      points.push([slot.slotPoints[point].y, slot.slotPoints[point].x])
      centY += slot.slotPoints[point].y
      centX += slot.slotPoints[point].x
    }
    var layer = L.polygon(points, { color: '#00d9ff' }).addTo(ele_layer["slot"]);
    add_new_layer_cb(layer)
    layer["ele_type"] = "slot"
    layer["ele_prop"] = {}
    layer["ele_prop"]["id"] = slot.id
    layer["ele_prop"]["h"] = slot.height
    layer["ele_prop"]["type"] = slot.type

    var textIcon = L.divIcon({
      className: 'inner-divIcon',
      html: slot.id,
      iconSize: 0
    })
    textIcon = L.marker([centY / 4, centX / 4], { icon: textIcon }).addTo(ele_layer["slot_id"])
    textIcon["ele_type"] = "slot_id"
    textIcon["ele_prop"] = { "id": slot.id }
    add_new_layer_cb(textIcon)
  }
  mymap.fitBounds(L.latLngBounds(temp_points), { animate: false })
}

function convert_queterion_to_yaw(q) {
  var siny_cosp = 2 * (q.w * q.z + q.x * q.y);
  var cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z);
  var yaw = Math.atan2(siny_cosp, cosy_cosp);
  return yaw
}

function rot_2d_vector_by_angle(v, angle) {
  var x = v[0]
  var y = v[1]
  var cos_a = Math.cos(angle)
  var sin_a = Math.sin(angle)
  var x1 = x * cos_a - y * sin_a
  var y1 = x * sin_a + y * cos_a
  return [x1, y1]
}

// read .rec file
function parse_rec_pb(buffer) {
  const YourMessage = proto_root.lookupType(proto_type["rec"]["mes"]);
  const message = YourMessage.decode(new Uint8Array(buffer));
  for (var feat_ind in message.features) {
    if (message.features[feat_ind].pose.floor == 0) {
      message.features[feat_ind].pose.floor = -999999
    }
  }
  console.log(message)
  document.getElementById("current_id_name").value = message.id
  remove_all_sub_layers(ele_layer["rec"])
  var temp_points = []
  for (var feat_ind in message.features) {
    var feat = message.features[feat_ind]
    var point = [feat.pose.pose.position.x, feat.pose.pose.position.y]
    var q = { "w": feat.pose.pose.rotation.w, "x": feat.pose.pose.rotation.x, "y": feat.pose.pose.rotation.y, "z": feat.pose.pose.rotation.z }
    var yaw = convert_queterion_to_yaw(q)
    var dir = rot_2d_vector_by_angle([0.5, 0], yaw)
    dir[0] = dir[0] + point[0]
    dir[1] = dir[1] + point[1]
    //draw line from point to dir
    var points = []
    points.push([point[1], point[0]])
    points.push([dir[1], dir[0]])
    L.polyline(points, { color: '#7733ee' }).addTo(ele_layer["rec"]);
    temp_points.push([point[1], point[0]])
    var layer = L.circle([point[1], point[0]], { color: '#7733ee', radius: 0.2 }).addTo(ele_layer["rec"]);
    add_new_layer_cb(layer)
    layer["ele_type"] = "rec"
    layer["ele_prop"] = {
      "floor": feat.pose.floor, "pos_x": feat.pose.pose.position.x,
      "pos_y": feat.pose.pose.position.y, "pos_z": feat.pose.pose.position.z
    }
  }
  // L.polyline(temp_points, { color: 'red' }).addTo(ele_layer["rec"]);
  mymap.fitBounds(L.latLngBounds(temp_points), { animate: false })
}

// read .nav file
function parse_nav_pb(buffer) {
  const YourMessage = proto_root.lookupType(proto_type["nav"]["mes"]);
  const message = YourMessage.decode(new Uint8Array(buffer));
  console.log(message)
  document.getElementById("current_id_name").value = message.id
  remove_all_sub_layers(ele_layer["traj"])
  remove_all_sub_layers(ele_layer["traj_topo"])

  // console.assert(message.verticles.length == message.targetVerticleIds.length + 1
  //   && message.targetVerticleIds.length == message.edges.length)

  var topo_len = Math.max(1, Math.abs(parseInt(document.getElementById("topo_length").value)))
  var topo_isEdge = document.getElementById("topo_isEdge").checked
  var temp_points = []
  // var st_ed_points = []
  for (var edge of message.edges) {
    // console.assert(edge["paths"].length + 1 == edge["verticleIds"].length)
    for (var path_id in edge["paths"]) {
      var points = []
      for (var navPoints of edge["paths"][path_id]["navPoints"]) {
        points.push([navPoints["pose"]["position"].y, navPoints["pose"]["position"].x])
        if (navPoints["type"] == "1") {
          L.circle([navPoints["pose"]["position"].y, navPoints["pose"]["position"].x],
            { color: '#ff0000', radius: 0.1 }).addTo(ele_layer["traj"])
        }
      }
      temp_points.push(points)
      // st_ed_points.push([points[0], points[points.length - 1]])
      if (topo_isEdge == false) {
        for (var i = 0; i < points.length - 1; i += topo_len) {
          drawArrow([points[i], points[i + 1]])
        }
      } else {
        drawArrow([points[0], points[points.length - 1]])
      }
      // console.log(points)
      var layer = L.polyline(points, { color: 'blue' }).addTo(ele_layer["traj"]);
      add_new_layer_cb(layer)
      layer["ele_type"] = "traj"
      layer["ele_prop"] = { "target_id": edge["paths"][path_id]["targetId"] }
      const circle_st = L.circle(points[0], {
        color: 'purple',
        fillColor: 'purple',
        fillOpacity: 0.8,
        radius: 1,
      }).addTo(ele_layer["traj_topo"])
      const circle_ed = L.circle(points[points.length - 1], {
        color: 'purple',
        fillColor: 'purple',
        fillOpacity: 0.8,
        radius: 1,
      }).addTo(ele_layer["traj_topo"])
    }
    mymap.fitBounds(L.latLngBounds(temp_points), { animate: false })
  }

  // for (var v of message.verticles) {
  //   console.log(v["id"])
  // }
  // for (var end_v of message.targetVerticleIds) {
  //   console.log(end_v)
  // }
}

function getArrowPos(st, angle) {
  var len = Math.abs(parseFloat(document.getElementById("topo_size").value))
  var resy = st[0] + len * Math.sin(angle)
  var resx = st[1] + len * Math.cos(angle)
  return [resy, resx]
}

function drawArrow(points) {
  var lineLayer = L.polyline(points, { color: 'red' }).addTo(ele_layer["traj_topo"])
  var yy = points[0][0] - points[1][0]
  var xx = points[0][1] - points[1][1]
  var angle = null
  const eps = 1e-8
  if (Math.abs(xx) < eps && Math.abs(yy) < eps) { // same
    angle = null
  } else if (Math.abs(xx) < eps && yy > 0) { // y+
    angle = Math.PI / 2
  } else if (Math.abs(xx) < eps && yy < 0) { // y-
    angle = -Math.PI / 2
  } else if (Math.abs(yy) < eps && xx > 0) { // x+
    angle = 0
  } else if (Math.abs(yy) < eps && xx < 0) { // x-
    angle = Math.PI
  } else if (xx > 0) {  // x+, y+-
    angle = Math.atan(yy / xx)
  } else if (yy > 0) {  // x-, y+
    angle = Math.atan(yy / xx) + Math.PI
  } else {    // x-, y-
    angle = Math.atan(yy / xx) - Math.PI
  }

  if (angle != null) {
    var arrow1 = getArrowPos(points[1], angle + Math.PI / 6)
    var arrow2 = getArrowPos(points[1], angle - Math.PI / 6)
    arrow1 = L.polyline([points[1], arrow1], { color: 'red' }).addTo(ele_layer["traj_topo"])
    arrow2 = L.polyline([points[1], arrow2], { color: 'red' }).addTo(ele_layer["traj_topo"])
  }
}

// read .loc file
function parse_loc_pb(buffer, isIndex = false) {
  const YourMessage = proto_root.lookupType(proto_type["loc"]["mes"]);
  const message = YourMessage.decode(new Uint8Array(buffer));
  console.log(message)
  document.getElementById("current_id_name").value = message.id

  remove_all_sub_layers(ele_layer["lidar"])
  remove_all_sub_layers(ele_layer["bound"])
  lidar_min_h = -1
  lidar_max_h = -1
  lidar_points_for_refresh = message.lidarPoints

  var temp_points = []
  for (var point_id in message.lidarPoints) {
    var points = message.lidarPoints[point_id]
    var pt = L.latLng(points.y, points.x)
    temp_points.push(pt)
    if (lidar_min_h == -1) {
      lidar_min_h = points.z
      lidar_max_h = points.z
    } else {
      if (lidar_min_h > points.z) {
        lidar_min_h = points.z
      }
      if (lidar_max_h < points.z) {
        lidar_max_h = points.z
      }
    }
  }
  //round to 2 decimal
  lidar_min_h = Math.round(lidar_min_h * 100) / 100
  lidar_max_h = Math.round(lidar_max_h * 100) / 100
  document.getElementById("h_range").innerHTML = "高度范围: " + lidar_min_h + "m ~ " + lidar_max_h + "m"
  refreshLidarPoint()
  if (temp_points.length > 0) {
    // console.log(L.latLngBounds(temp_points))
    mymap.fitBounds(L.latLngBounds(temp_points), { animate: false })
  }
}

function saveSemanticMap() {
  var id_name = document.getElementById("current_id_name").value
  var temp_sem_obj = { id: id_name, parkingSlots: [] }
  ele_layer["slot"].eachLayer((obj) => {
    var slot_obj = { slotPoints: [] }
    // console.log(obj)
    slot_obj["slotPoints"].push({ "x": obj.getLatLngs()[0][0].lng, "y": obj.getLatLngs()[0][0].lat })
    slot_obj["slotPoints"].push({ "x": obj.getLatLngs()[0][1].lng, "y": obj.getLatLngs()[0][1].lat })
    slot_obj["slotPoints"].push({ "x": obj.getLatLngs()[0][2].lng, "y": obj.getLatLngs()[0][2].lat })
    slot_obj["slotPoints"].push({ "x": obj.getLatLngs()[0][3].lng, "y": obj.getLatLngs()[0][3].lat })
    slot_obj["type"] = obj["ele_prop"]["type"]
    slot_obj["height"] = obj["ele_prop"]["h"]
    slot_obj["id"] = obj["ele_prop"]["id"]
    temp_sem_obj["parkingSlots"].push(slot_obj)
  })
  // console.log(temp_sem_obj["parkingSlots"].length)
  if (temp_sem_obj["parkingSlots"].length > 0) {
    serialize_js_obj_to_proto3(temp_sem_obj, "sem")
  }
}

function saveNavigationMap() {
  var id_name = document.getElementById("current_id_name").value
  var temp_traj_obj = { id: id_name, edges: [], verticles: [], targetVerticleIdls: [] }
  ele_layer["traj"].eachLayer((obj) => {
    var traj_obj = { navPoints: [] }
    var temp_len = 0
    var last_pt = null
    for (var i = 0; i < obj.getLatLngs().length; i++) {
      var pose = { "position": { "x": obj.getLatLngs()[i].lng, "y": obj.getLatLngs()[i].lat } }
      var temp_dist = 0
      if (last_pt != null) {
        temp_dist = Math.sqrt(Math.pow(obj.getLatLngs()[i].lng - last_pt.lng, 2) + Math.pow(obj.getLatLngs()[i].lat - last_pt.lat, 2))
      }
      last_pt = obj.getLatLngs()[i]
      temp_len += temp_dist
      traj_obj["navPoints"].push({ "pose": pose, "length": temp_len })
      traj_obj["targetId"] = obj["ele_prop"]["target_id"]
    }
    temp_traj_obj["edges"].push({ "paths": [] })
    temp_traj_obj["edges"][temp_traj_obj["edges"].length - 1]["paths"].push(traj_obj)
  })
  if (temp_traj_obj["edges"].length > 0) {
    serialize_js_obj_to_proto3(temp_traj_obj, "nav")
  }

  // var temp_points = []
  // var st_ed_points = []
  // for (var edge of message.edges) {
  //   console.assert(edge["paths"].length + 1 == edge["verticleIds"].length)
  //   for (var path_id in edge["paths"]) {
  //     var points = []
  //     for (var navPoints of edge["paths"][path_id]["navPoints"]) {
  //       points.push([navPoints["pose"]["position"].x, navPoints["pose"]["position"].y])
  //     }
  //     temp_points.push(points)
  //     st_ed_points.push([points[0], points[points.length - 1]])
  //     // console.log(points)
  //     var layer = L.polyline(points, { color: 'blue' }).addTo(ele_layer["traj"]);
  //     add_new_layer_cb(layer)
  //     layer["ele_type"] = "traj"
  //     layer["ele_prop"] = { "target_id": edge["verticleIds"][parseInt(path_id) + 1] }  // 每个path终点作为path_id
  //   }
  //   mymap.fitBounds(L.latLngBounds(temp_points), { animate: false })
  // }
}


function saveLocationMap() {
  var id_name = document.getElementById("current_id_name").value
  var tmp_loc_obj = { id: id_name, boundary: [], lidarPoints: [], mapLocation: {} }
  for (var i = 0; i < lidar_points_for_refresh.length; i++) {
    var lidar_point_obj = {
      "x": lidar_points_for_refresh[i].x, "y": lidar_points_for_refresh[i].y,
      "z": lidar_points_for_refresh[i].z, "type": lidar_points_for_refresh[i].type,
    }
    tmp_loc_obj["lidarPoints"].push(lidar_point_obj)
  }
  ele_layer["bound"].eachLayer((obj) => {
    for (var i = 0; i < obj.getLatLngs()[0].length; i++) {
      tmp_loc_obj["boundary"].push({ "x": obj.getLatLngs()[0][i].lng, "y": obj.getLatLngs()[0][i].lat })
    }
  })

  // UTM_rects_points_layer["points"].eachLayer((obj) => {
  //   // tmp_loc_obj["mapLocation"] = { "y": obj.getLatLng().lat, "x": obj.getLatLng().lng, "z": 0 }
  //   var utm_p = WGS2UTM( obj.getLatLng().lng, obj.getLatLng().lat)
  //   // console.log(utm_p)
  //   tmp_loc_obj["mapLocation"] = { "x": utm_p[0], "y": utm_p[1], "z": 0 }
  // })

  // if (tmp_loc_obj["lidarPoints"].length > 0 || tmp_loc_obj["boundary"].length > 0 || Object.keys(tmp_loc_obj["mapLocation"]).length > 0) {
  //   serialize_js_obj_to_proto3(tmp_loc_obj, "loc")
  // }
  if (tmp_loc_obj["lidarPoints"].length > 0 || tmp_loc_obj["boundary"].length > 0) {
    serialize_js_obj_to_proto3(tmp_loc_obj, "loc")
  }
}
